#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import re
import sys
import time
import json
import glob
import socket
import shutil
import pathlib
import subprocess
import atomicwrites
import http.client
import urllib.error
import urllib.request
import urllib3
import urllib3.exceptions
import robust_layer.simple_git
from datetime import datetime

import mirrors.plugin


class Main:

    def __init__(self, sock):
        self.DATE_FMT = "%Y-%m-%d"

        self.sock = sock
        self.dataDir = mirrors.plugin.params["storage-file"]["data-directory"]
        self.p = InfoPrinter()
        self.progress = 0

    def run(self):
        # download from the original bing site
        # we can download only a few days from here
        self._downloadFromBing(20)

        # download from https://github.com/niumoo/bing-wallpaper
        # this site has zh-CN bing wallpapers up to 2021-02-01, it has only 4K image file, no other resolution and metadata
        self._downloadFromNiumoo(40)

        # download from https://github.com/ameizi/bing-wallpaper
        # this site has zh-CN bing wallpapers up to 2021-04-10, it has only limited resolutions and metadata
        self._downloadFromAmeizi(60)

        # download from https://github.com/asvow/bing-wallpaper
        # this site has zh-CN and en-US bing wallpapers up to 2021-12-07, it has only limited resolutions and metadata
        self._downloadFromAsvow(100)

    def _downloadFromBing(self, endProgress):
        bingUrl = "https://bing.com"
        bingImagePath = "/HPImageArchive.aspx?format=js&idx=0&n=8&uhd=1&mkt=%s"       # format: json; start index: 0, fetch count: 8

        self.p.print("Downloading from %s" % (bingUrl))
        self.p.incIndent()
        try:
            if self.__isInChinaMainland():
                marketList = [
                    'zh-CN',
                ]
            else:
                marketList = [
                    'ar-XA', 'da-DK',
                    'de-AT', 'de-CH', 'de-DE',
                    'en-AU', 'en-CA', 'en-GB', 'en-ID', 'en-IE', 'en-IN', 'en-MY', 'en-NZ', 'en-PH', 'en-SG', 'en-US', 'en-WW', 'en-XA', 'en-ZA',
                    'es-AR', 'es-CL', 'es-ES', 'es-MX', 'es-US', 'es-XL',
                    'et-EE', 'fi-FI',
                    'fr-BE', 'fr-CA', 'fr-CH', 'fr-FR',
                    'he-IL', 'hr-HR', 'hu-HU', 'it-IT', 'ja-JP', 'ko-KR', 'lt-LT', 'lv-LV', 'nb-NO',
                    'nl-BE', 'nl-NL',
                    'pl-PL',
                    'pt-BR', 'pt-PT',
                    'ro-RO', 'ru-RU', 'sk-SK', 'sl-SL', 'sv-SE', 'th-TH', 'tr-TR', 'uk-UA',
                    'zh-CN', 'zh-HK', 'zh-TW',
                ]

            for market in marketList:
                self.p.print("Processing market %s" % (market))
                self.p.incIndent()
                try:
                    ret = self.__getJson(bingUrl + bingImagePath % (market))
                    for image in ret["images"]:
                        curDate = datetime.strptime(image["enddate"], "%Y%m%d")
                        imgUrl = bingUrl + re.sub("w=[0-9]+&h=[0-9]+", "", image["url"])    # remove w= and h=, so that we download the maximum resolution
                        self._processDate(market, curDate, imgUrl, {
                            "title": image["title"],
                            "url": imgUrl,
                            "copyright": image["copyright"],
                            "wp": image["wp"],                      # wp == false when background is animated
                            "hsh": image["hsh"],
                        })
                finally:
                    self.p.decIndent()
        finally:
            self.p.decIndent()

        self.sock.progress_changed(endProgress)

    def _downloadFromNiumoo(self, endProgress):
        url = "https://github.com/niumoo/bing-wallpaper"
        cacheDir = os.path.join(self.dataDir, ".niumoo_bing-wallpapers.git")
        marketList = ['en-US', 'zh-CN']

        self.p.print("Downloading from %s" % (url))
        self.p.incIndent()
        try:
            self.p.print("Updating repository")
            robust_layer.simple_git.pull(cacheDir, reclone_on_failure=True, url=url)

            for market in marketList:
                self.p.print("Processing market %s" % (market))
                self.p.incIndent()
                try:
                    buf = pathlib.Path(os.path.join(cacheDir, "bing-wallpaper.md")).read_text()
                    for m in re.finditer(r"([0-9]{4}-[0-9]{2}-[0-9]{2}) \| \[(.*)]\((.*)\)", buf):
                        if market.upper() not in m.group(3):                                                    # it sucks that both en-US and zh-CN links exist in bing-wallpaper.md
                            continue
                        curDate = datetime.strptime(m.group(1), "%Y-%m-%d")
                        imgUrl = self.__getStandardBingUrl(m.group(3))
                        self._processDate(market, curDate, imgUrl, {
                            "url": imgUrl,
                            "copyright": m.group(2),
                        })
                finally:
                    self.p.decIndent()
        finally:
            self.p.decIndent()

        self.sock.progress_changed(endProgress)

    def _downloadFromAmeizi(self, endProgress):
        url = "https://github.com/ameizi/bing-wallpaper"
        cacheDir = os.path.join(self.dataDir, ".ameizi_bing-wallpaper.git")
        marketList = ['en-US', 'zh-CN']

        self.p.print("Downloading from %s" % (url))
        self.p.incIndent()
        try:
            self.p.print("Updating repository")
            robust_layer.simple_git.pull(cacheDir, reclone_on_failure=True, url=url)

            for market in marketList:
                self.p.print("Processing market %s" % (market))
                self.p.incIndent()
                try:
                    buf = pathlib.Path(os.path.join(cacheDir, "bing-wallpaper.md")).read_text()
                    for m in re.finditer(r"([0-9]{4}-[0-9]{2}-[0-9]{2}) \| \[(.*)\]\((.*)\)", buf, re.M):
                        if market.upper() not in m.group(3):                                                    # it sucks that both en-US and zh-CN links exist in bing-wallpaper.md
                            continue
                        curDate = datetime.strptime(m.group(1), "%Y-%m-%d")
                        imgUrl = self.__getStandardBingUrl(m.group(3))
                        self._processDate(market, curDate, imgUrl, {
                            "url": imgUrl,
                            "copyright": m.group(2)
                        })
                finally:
                    self.p.decIndent()
        finally:
            self.p.decIndent()

        self.sock.progress_changed(endProgress)

    def _downloadFromAsvow(self, endProgress):
        url = "https://github.com/asvow/bing-wallpaper"
        cacheDir = os.path.join(self.dataDir, ".asvow_bing-wallpapers.git")
        marketDict = {
            'zh-CN': "bing.json",
            'en-US': "bing_en.json",
        }

        self.p.print("Downloading from %s" % (url))
        self.p.incIndent()
        try:
            self.p.print("Updating repository")
            robust_layer.simple_git.pull(cacheDir, reclone_on_failure=True, url=url)

            for market, fn in marketDict.items():
                self.p.print("Processing market %s" % (market))
                self.p.incIndent()
                try:
                    for fullfn in glob.glob(os.path.join(cacheDir, "**", fn), recursive=True):
                        fullJson = None
                        with open(fullfn) as f:
                            fullJson = json.load(f)

                        for k, v in fullJson.items():
                            curDate = datetime.strptime(k, "%Y%m%d")
                            imgUrl = self.__getStandardBingUrl(v["url"])
                            self._processDate(market, curDate, imgUrl, {
                                "url": imgUrl,
                                "copyright": v["copyright"],
                            })
                finally:
                    self.p.decIndent()
        finally:
            self.p.decIndent()

        self.sock.progress_changed(endProgress)

    def _processDate(self, market, curDate, imgUrl, jsonData):
        curDateStr = curDate.strftime(self.DATE_FMT)
        self.p.print("Processing date %s (%s)" % (curDateStr, imgUrl))
        self.p.incIndent()
        try:
            if market.upper() not in imgUrl:
                self.p.print("Image URL (%s) and market not match, ignore." % (imgUrl))
                return

            dateDir = os.path.join(self.dataDir, curDateStr)
            marketDir = os.path.join(dateDir, market)
            jsonFullfn = os.path.join(marketDir, "metadata.json")

            # create dateDir and marketDir
            Util.forceMakeDir(marketDir)

            # already downloaded?
            if os.path.exists(jsonFullfn):
                try:
                    ret = self.__compareMetadataJson(jsonFullfn, jsonData)
                    if ret <= 0:
                        self.p.print("Keep downloaded data.")
                        return
                    else:
                        self.p.print("Metadata invalid, purge downloaded data and download again...")
                except MetadataJsonCompareError as e:
                    self.p.print("Keep downloaded data, but key \"%s\" in metadata.json seems incorrect" % (e.key))
                    self.p.print("  Old value: %s" % (e.oldValue))
                    self.p.print("  New value: %s" % (e.newValue))
                    return
            else:
                self.p.print("Download data...")

            # download and record
            Util.forceClearDir(marketDir)

            # download image(s)
            localFn = re.search("id=(.*?)(&|$)", imgUrl).group(1)
            localFn = os.path.join(marketDir, localFn)
            self.__downloadFile(imgUrl, localFn)

            # write metadata
            with atomicwrites.atomic_write(jsonFullfn) as f:
                json.dump(jsonData, f)
        finally:
            self.p.decIndent()

    def __isInChinaMainland(self):
        target = urllib3.PoolManager().request("GET", "https://bing.com", redirect=False).get_redirect_location()
        return target == "https://cn.bing.com" or target.startswith("https://cn.bing.com/")

    def __getJson(self, url):
        while True:
            try:
                return json.load(urllib.request.urlopen(url))
            except socket.timeout as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except http.client.HTTPException as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.HTTPError as e:
                if e.code == 404:
                    raise
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)
            except urllib.error.URLError as e:
                self.p.print("urlopen failed and try again: %s" % str(e))
                time.sleep(10.0)

    def __getStandardBingUrl(self, url):
        return re.sub(r"https?://.*bing\.com/", "https://bing.com/", url)

    def __compareMetadataJson(self, fullfn, newJson):
        oldJson = None
        with open(fullfn) as f:
            oldJson = json.load(f)

        keyList = ["title", "url", "copyright", "wp", "hsh"]
        for key in keyList:
            if key not in newJson:
                if key not in oldJson:
                    return 0
                else:
                    return -1
            else:
                if key not in oldJson:
                    return 1
                else:
                    if newJson[key] != oldJson[key]:
                        raise MetadataJsonCompareError(key, oldJson[key], newJson[key])
                    return 0

    def __downloadFile(self, url, localFile):
        while True:
            try:
                subprocess.check_call(["wget", "--quiet", "--no-check-certificate", "-O", localFile, url])      # always re-dowloand
                break
            except subprocess.CalledProcessError as e:
                if e.returncode == 8:       # not found
                    raise
                self.p.print("download failed and try again: %s" % str(e))
                time.sleep(60)


class MetadataJsonCompareError(Exception):

    def __init__(self, key, oldValue, newValue):
        super().__init__()
        self.key = key
        self.oldValue = oldValue
        self.newValue = newValue


class InfoPrinter:

    def __init__(self):
        self.indent = 0

    def incIndent(self):
        self.indent = self.indent + 1

    def decIndent(self):
        assert self.indent > 0
        self.indent = self.indent - 1

    def print(self, s):
        line = ""
        line += "\t" * self.indent
        line += s
        print(line)


class Util:

    @staticmethod
    def forceDelete(path):
        if os.path.islink(path):
            os.remove(path)
        elif os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        elif os.path.lexists(path):
            os.remove(path)             # other type of file, such as device node
        else:
            pass                        # path does not exist, do nothing

    @staticmethod
    def forceMakeDir(path):
        if not os.path.exists(path):
            os.makedirs(path)
        elif not os.path.isdir(path):
            Util.forceDelete(path)
            os.mkdir(path)
        else:
            pass

    @staticmethod
    def forceClearDir(path):
        for fn in os.listdir(path):
            Util.forceDelete(os.path.join(path, fn))


###############################################################################

if __name__ == "__main__":
    with mirrors.plugin.ApiClient() as sock:
        try:
            Main(sock).run()
            sock.progress_changed(100)
        except Exception:
            sock.error_occured(sys.exc_info())
            raise





# bingUrl = "https://bingwallpaper.anerg.com"
# self.URL2 = "https://bing.gifposter.com/archive.html"
# self.URL5 = "http://bing.yiyuen.com/bing"
# self.URL6 = "http://bing.ioliu.cn"					# github.com/xCss/bing



# genzj/pybingwallpaper





# https://github.com/neffo/bing-wallpaper-gnome-extension
# https://github.com/Starcross/bing-wallpaper-cinnamon
